node:url: run domainToUnicode regardless of string backing store#33756
node:url: run domainToUnicode regardless of string backing store#33756robobun wants to merge 2 commits into
Conversation
jsDomainToUnicode returned its argument verbatim whenever the input WTF::String was 16-bit backed. is8Bit() is a representation property, not a value property: an all-ASCII punycode label can be stored in a 16-bit buffer (domainToASCII always returns one, as do slices of a 16-bit rope), and UTS #46 ToUnicode is defined over all inputs, not only Latin-1 ones. The result was that domainToUnicode(domainToASCII(d)) never decoded, and two === strings could produce different outputs. Drop the fast path and always call uidna_nameToUnicode, matching the structure jsDomainToASCII already uses.
|
Reproduced with: bun -e 'import {domainToASCII as a, domainToUnicode as u} from "node:url"; const x="xn--bcher-kva.de", y=(x+"中").slice(0,-1); console.log(x===y, u(x), u(y), u(a("bücher.de")))'Fix in #33756. CI: |
|
Updated 9:38 AM PT - Jul 8th, 2026
❌ @robobun, your commit 7ca8fa5 has 3 failures in
🧪 To try this PR locally: bunx bun-pr 33756That installs a local version of the PR into your bun-33756 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThis PR changes ChangesdomainToUnicode IDNA Conversion Fix
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
LGTM — removes a demonstrably incorrect fast path and mirrors the existing jsDomainToASCII structure, with tests covering the representation-dependence, round-trip, and mixed-label cases.
Extended reasoning...
Overview
This PR touches two files: a 3-line functional change in src/jsc/bindings/NodeURL.cpp (removing the !domain.is8Bit() early-return in jsDomainToUnicode and replacing it with the same if (is8Bit()) convertTo16Bit() pattern used by jsDomainToASCII directly above), and four new test cases appended to test/js/node/url/url-domain-ascii-unicode.test.js.
The root cause is well-argued and correct: WTF::String::is8Bit() reflects the backing store, not the value, so the old fast path made domainToUnicode return different results for ===-equal strings and broke the documented domainToUnicode(domainToASCII(x)) round trip (since domainToASCII builds its result from a char16_t[]). The fix unconditionally routes through uidna_nameToUnicode, which is what UTS #46 ToUnicode requires anyway.
Security risks
None. This is a Node-compat IDNA decoding helper; the change removes a short-circuit and hands the same input to the same ICU function that already handles the 8-bit case. No new allocation sizing, no new parsing of untrusted lengths, no auth/crypto/permissions surface.
Level of scrutiny
Low. The diff is tiny, mechanical, and mirrors the sibling function line-for-line. The new code path (span16() → uidna_nameToUnicode) is the exact path already exercised by every 8-bit input after convertTo16Bit(), so there's no genuinely new behavior being introduced — just the removal of an incorrect bypass.
Other factors
The added tests are strong: they assert the specific representation-independence property (two === strings produce the same output), the round-trip over the existing 50+ pair fixture table, mixed A-label/U-label inputs, and UTS #46 mapping on non-Latin-1 input. The PR description verifies the new tests fail on released Bun and pass with the change, and notes the relationship to the broader #33206 rewrite. No outstanding reviewer comments, no bugs flagged by the bug-hunting pass.
url.domainToUnicodereturned its argument verbatim whenever the inputWTF::Stringhappened to be 16-bit backed, so the documented round trip never decoded and two===strings could produce different outputs.Repro
Cause
jsDomainToUnicodeinsrc/jsc/bindings/NodeURL.cppshort-circuited on!domain.is8Bit():is8Bit()is a representation property ofWTF::String, not a value property. An all-ASCII punycode host can be stored in a 16-bit buffer:domainToASCIIalways returns one (it builds the result from achar16_t[]), and so do slices of any 16-bit rope and literals from a source file containing non-Latin-1 text. A 16-bit string can also containxn--A-labels mixed with already-decoded labels ("中.xn--fiqs8s"), and UTS #46 ToUnicode is defined over all inputs, including mapping and case folding of code points above U+00FF.Fix
Drop the fast path and always call
uidna_nameToUnicode, matching the structurejsDomainToASCIIalready uses for the 8-bit/16-bit split.Verification
test/js/node/url/url-domain-ascii-unicode.test.js: 134 pass. The four new tests fail on released Bun and pass with this change; the 130 pre-existing tests are unchanged.#33206 is a broader rewrite of both
domainToASCIIanddomainToUnicodethrough the WHATWG host parser that also removes this fast path; this PR is the minimal targeted fix for the representation-dependent result and conflicts only on the three lines it deletes.